#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <cmath>
#include <utility>
#include <vector>
#include "lims/entities/Point.hpp"
#include "lims/entities/Reference.hpp"
#include "lims/common/Constants.h"

using namespace std;

namespace MetricSpaceBenchmark::MLIndex::LIMS {

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // load data set from filename.txt
    class InputReader {
    public:
        ifstream fin;              // 输入文件流,用于读取文件
        vector<Point> points;      // 存储读取的点集合

        InputReader() = default;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        explicit InputReader(const string &filename) {
            fin.open(filename.c_str());
            if (!fin) {
                cout << filename << " file could not be opened\n";
                exit(0);
            }
            parse();
            fin.close();
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // 从文件中加载范围查询的数据
        vector<Point> LoadRangeQuery(const string &filename) {
            fin.open(filename.c_str());
            if (!fin) {
                cout << filename << " file could not be opened\n";
                exit(0);
            }

            string line;
            int i = 0;
            Point query_bound;
            while (getline(fin, line)) {
                stringstream ss(line);
                string value;
                vector<double> data;
                vector<double> coordinate;
                while (getline(ss, value, ','))
                    data.push_back(atof(value.c_str()));

                Point point = Point(data);
                if (i % 2 == 0)
                    query_bound = point;
                else {
                    for (unsigned j = 0; j < data.size(); j++) {
                        coordinate.push_back((query_bound.coordinate[j] + point.coordinate[j]) / 2);
                    }
                    points.emplace_back(coordinate);
                }
                i++;
            }
            return points;
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        vector<Point> LoadPointQuery(const string &filename) {
            fin.open(filename.c_str());
            if (!fin) {
                cout << filename << " file could not be opened\n";
                exit(0);
            }
            string line;
            while (getline(fin, line)) {
                stringstream ss(line);
                string value;
                vector<double> data;
                while (getline(ss, value, ','))
                    data.push_back(atof(value.c_str()));

                Point point = Point(data);
                points.push_back(point);
            }
            return points;
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // data format : id \t coordinate1 \t coordinate2 \t...,
        void parse() {
            string line;
            while (getline(fin, line)) {
                stringstream ss(line);
                string value;
                vector<double> coordinate;
                getline(ss, value, ',');
                unsigned id = stol(value);
                while (getline(ss, value, ',')) {
                    coordinate.push_back(atof(value.c_str()));
                }
                Point point = Point(coordinate, id);
                points.push_back(point);
            }
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // return cluster point set
        Clu_Point getCluster() {
            Clu_Point clu_point = Clu_Point(points);
            return clu_point;
        }
    };

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    class OutputPrinter {
    public:
        ofstream fout;
        Clu_Point cluster;
        vector<Point> result;
        string outputFileName;
        mainRef_Point mainRef_point;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // 输出一维值
        OutputPrinter() = default;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        OutputPrinter(const string &filename, Clu_Point clu) {
            this->cluster = std::move(clu);
            if (filename.size() < 4) {
                cout << filename << " input file name's format is wrong" << endl;
                exit(0);
            }
            this->outputFileName = filename;
            print();
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void print() {
            fout.open(outputFileName.c_str());

            for (auto &i: cluster.clu_point) {
                fout << i.i_value << endl;
            }

            fout.close();
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // 输出结果点
        OutputPrinter(const string &filename, vector<Point> result) {
            this->result = std::move(result);
            if (filename.size() < 4) {
                cout << filename << " input file name's format is wrong" << endl;
                exit(0);
            }
            this->outputFileName = filename;
            print_result();
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void print_result() {
            fout.open(outputFileName.c_str());

            for (unsigned i = 0; i < result.size(); i++) {
                fout << i << "\t" << result[i].i_value;
                // for(unsigned j = 0; j < result[i].coordinate.size(); j++){
                //     fout << "\t" << result[i].coordinate[j];
                // }
                fout << endl;
            }

            fout.close();
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // 输出距离环号对应值
        OutputPrinter(string filename, mainRef_Point &mainRef_point) {
            this->mainRef_point = mainRef_point;
            if (filename.size() < 4) {
                cout << filename << " input file name's format is wrong" << endl;
                exit(0);
            }
            for (int i = 0; i < 4; i++)
                filename.pop_back();
            this->outputFileName = filename;
            print_refCircle();
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void print_refCircle() {
            fout.open(outputFileName + "_cluster12_ref0.txt");
            int len = mainRef_point.dis.size();
            sort(mainRef_point.dis.begin(), mainRef_point.dis.end());

            for (int i = 0; i < len; i++) {
                for (unsigned j = 0; j < mainRef_point.dict_circle.size(); j++) {
                    if (mainRef_point.dis[i] >= mainRef_point.dict_circle[j][0] &&
                        mainRef_point.dis[i] <= mainRef_point.dict_circle[j][1]) {
                        fout << mainRef_point.dis[i] << "\t" << to_string(j) << endl;
                        break;
                    }
                }
            }
            fout.close();
            for (unsigned x = 0; x < mainRef_point.ref_points.size(); x++) {
                fout.open(outputFileName + "_cluster12_ref" + to_string(x + 1) + ".txt");

                sort(mainRef_point.ref_points[x].dis.begin(), mainRef_point.ref_points[x].dis.end());

                for (int i = 0; i < len; i++) {
                    for (unsigned j = 0; j < mainRef_point.ref_points[x].dict_circle.size(); j++) {
                        if (mainRef_point.ref_points[x].dis[i] >= mainRef_point.ref_points[x].dict_circle[j][0] &&
                            mainRef_point.ref_points[x].dis[i] <= mainRef_point.ref_points[x].dict_circle[j][1]) {
                            fout << mainRef_point.ref_points[x].dis[i] << "\t" << to_string(j) << endl;
                            break;
                        }
                    }
                }

                fout.close();
            }
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        OutputPrinter(const string &filename, vector<double> &res) {
            if (filename.size() < 4) {
                cout << filename << " input file name's format is wrong" << endl;
                exit(0);
            }
            fout.open(filename.c_str());

            for (double re: res) {
                fout << re << endl;
            }

            fout.close();
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void print_errResult(const string &filename, vector<double> &err_res) {
            if (filename.size() < 4) {
                cout << filename << " input file name's format is wrong" << endl;
                exit(0);
            }
            fout.open(filename.c_str());

            for (double err_re: err_res) {
                fout << err_re << endl;
            }

            fout.close();
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        void print_iValue(const string &filename, vector<Point> &pts) {
            if (filename.size() < 4) {
                cout << filename << " input file name's format is wrong" << endl;
                exit(0);
            }
            fout.open(filename.c_str());

            for (auto &pt: pts) {
                fout << pt.i_value << endl;
            }

            fout.close();
        }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        // 按照一维值顺序以二进制存储数据文件
        OutputPrinter(int clu_id, mainRef_Point &mainRef_point) {
            unsigned page_num = ceil((double) mainRef_point.iValuePts.size() / Constants::PAGE_SIZE);
            unsigned count = 0;
            int size_dim = mainRef_point.iValuePts[0].coordinate.size();
            for (unsigned i = 0; i < page_num; i++) {
                this->outputFileName = "./data/cluster_" + to_string(clu_id) + "_" + to_string(i);
                fout.open(outputFileName, ios::binary);
                int size_page =
                        count + Constants::PAGE_SIZE > mainRef_point.iValuePts.size() ? mainRef_point.iValuePts.size() -
                                                                                        count : Constants::PAGE_SIZE;
                // 写入二进制文件的页头信息,包括:
                // - size_page: 当前页包含的点数, 占用 4 字节
                // - size_dim: 每个点的维度大小, 占用 4 字节
                fout.write((const char *) &size_page, 4);
                fout.write((const char *) &size_dim, 4);
                // 循环写入当前页的点数据
                for (int j = 0; j < size_page; ++j)
                    fout.write(reinterpret_cast<char *>(mainRef_point.iValuePts[count + j].coordinate.data()),
                               size_dim * sizeof(mainRef_point.iValuePts[count + j].coordinate.front()));
                fout.close();
                count += Constants::PAGE_SIZE;
            }
        }
    };
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
